跳到主要内容

JDK8 之后的新特性记录

var 关键字

JEP 286 提案让 Java 增加了局部类型推断(Local-Variable Type Inference)功能(JDK 10 之后),这让 Java 可以像 Js 里的 var 或者其他语言的 auto 一样可以自动推断数据类型。这其实只是一个新的语法糖,底层并没有变化,在编译时就已经把 var 转化成具体的数据类型了,但是这样可以减少代码的编写。

var hashMap = new HashMap<String, String>();
hashMap.put("key","temp");
var string = "hello java 10";
var stream = Stream.of(1, 2, 3, 4);
var list = new ArrayList<String>();

反编译编译后的这段代码,还是熟悉的代码片段。

HashMap<String, String> hashMap = new HashMap();
hashMap.put("key","temp");
String string = "hello java 10";
Stream<Integer> stream = Stream.of(1, 2, 3, 4);
ArrayList<String> list = new ArrayList();

var 看似好用,其实也有很多限制,官方介绍了 var 只能用于下面的几种情况。

  • 仅限带有初始化的程序的局部变量。
  • for 循环或者增强for 循环中。
  • for 循环中的声明。

下面演示三种使用情况。

public static void testVar() {
// 情况1,没有初始化会报错
// var list;
var list = List.of(1, 2, 3, 4);
// 情况2
for (var integer : list) {
System.out.println(integer);
}
// 情况3
for (var i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}

try-with-resources 语句

这个是 Java 7 的特性,但是一直没有搞明白,所以单独放在这里说

从Java 7 build 105 版本开始,Java 7 的编译器和运行环境支持新的 try-with-resources 语句,称为 ARM 块(Automatic Resource Management) ,自动资源管理。

新的语句支持包括流以及任何可关闭的资源,例如,以前我们会编写如下代码来释放资源:

private static void testTry(File source, File target) {
InputStream fis = null;
OutputStream fos = null;
try {
fis = new FileInputStream(source);
fos = new FileOutputStream(target);

byte[] buf = new byte[8192];

int i;
while ((i = fis.read(buf)) != -1) {
fos.write(buf, 0, i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
close(fis);
close(fos);
}
}

private static void close(Closeable closable) {
if (closable != null) {
try {
closable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

代码挺复杂的,异常的管理很麻烦。

而使用 try-with-resources 语句来简化代码如下:

private static void customBufferStreamCopy(File source, File target) {
try (InputStream fis = new FileInputStream(source);
OutputStream fos = new FileOutputStream(target)){

byte[] buf = new byte[8192];

int i;
while ((i = fis.read(buf)) != -1) {
fos.write(buf, 0, i);
}
}
catch (Exception e) {
e.printStackTrace();
}
}

在这个例子中,数据流会在 try 执行完毕后自动被关闭,前提是,这些可关闭的资源必须实现 java.lang.AutoCloseable 接口。

JDK9 提供的 of 方法

Java 9 中,以下方法被添加到 List,Set 和 Map 接口以及它们的重载对象。它们可以创建一个只读、不可改变的集合,必须构造和分配它,然后添加元素,最后包装成一个不可修改的集合。

static <E> List<E> of(E e1, E e2, E e3);
static <E> Set<E> of(E e1, E e2, E e3);
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3);
static <K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries)

List 和 Set 接口, of(...) 方法重载了 0 ~ 10 个参数的不同方法 。 Map 接口, of(...) 方法重载了 0 ~ 10 个参数的不同方法 。 Map 接口如果超过 10 个参数, 可以使用 ofEntries(...) 方法。

public class Tester {

public static void main(String []args) {
Set<String> set = Set.of("A", "B", "C");
System.out.println(set);
List<String> list = List.of("A", "B", "C");
System.out.println(list);
Map<String, String> map = Map.of("A","Apple","B","Boy","C","Cat");
System.out.println(map);

Map<String, String> map1 = Map.ofEntries (
new AbstractMap.SimpleEntry<>("A","Apple"),
new AbstractMap.SimpleEntry<>("B","Boy"),
new AbstractMap.SimpleEntry<>("C","Cat"));
System.out.println(map1);
}
}

其实就是添加给各个集合接口添加了 of() 方法 ,用于定义三种集合的不可变实例,而它们的参数,就是不可变实例的所有元素

但是,这些方法还是有一些限制的

1、对于 List 和 Set 和 Map 三个接口的 of() 方法, 重载方法的参数有 0 ~ 10 个不等。

2、对于 List 和 Set 和 of() 方法,有些重载还包含了一个参数 args 用于接受数量不定的值,这样就可以创建包含任意数量的 List 、 Set

3、如果要创建的不可变哈希 ( HashMap ) 的数量超过了 10 个,就不能再用 of() 方法了,而需要使用 ofEntries 方法

foreach 循环

这个 foreach 循环是 JDK5 的特性

foreach 的方法会每次都执行吗?

例如下面的代码:

for (String param : url.getQuery().split("&")) {
// do something...
}

会每次都执行这个 split 方法吗?

答案是否定的。

for each 走的是迭代器,虽然这里是返回的数组,但是会被优化成迭代器的语义,所以是只执行一次的

public class Test {
public static void main(String[] args) {
String str = "123&456&789";
for (String param : str.split("&")) {
System.out.println(param);
}
}
}

可以在这个 split 方法里面打个断点

可以看到它只执行了一次

镜像问题 for 循环的第二个块是否会每次都执行?

答案是肯定的

单个 catch 块中处理多个异常

在 JDK1.7 以上的版本catch 代码块得到了升级,支持使用 | 分隔多个异常,用以在 单个 catch 块中处理多个异常。如果你要捕获多个异常并且它们包含相似的代码,使用这一特性将会减少代码重复度。

下面用一个例子来理解。

catch (IOException ex) {
logger.error(ex);
throw new MyException(ex.getMessage());
catch (SQLException ex) {
logger.error(ex);
throw new MyException(ex.getMessage());
}

在 JDK7 之后版本可以

catch(IOException | SQLException ex){
logger.error(ex);
throw new MyException(ex.getMessage());
}

如果用一个 catch 块处理多个异常,可以用管道符(|)将它们分开,在这种情况下异常参数变量(ex)是定义为 final 的,所以不能被修改。这一特性将生成更少的字节码并减少代码冗余。

另一个升级是编译器对重新抛出异常(rethrown exceptions)的处理。这一特性允许在一个方法声明的 throws 从句中指定更多特定的异常类型。

package com.journaldev.util;

public class Java7MultipleExceptions {

public static void main(String[] args) {
try{
rethrow("abc");
}catch(FirstException | SecondException | ThirdException e){
//below assignment will throw compile time exception since e is final
//e = new Exception();
System.out.println(e.getMessage());
}
}

static void rethrow(String s) throws FirstException, SecondException, ThirdException {
try {
if (s.equals("First"))
throw new FirstException("First");
else if (s.equals("Second"))
throw new SecondException("Second");
else
throw new ThirdException("Third");
} catch (Exception e) {
// 下面的赋值没有启用重新抛出异常的类型检查功能,这是Java 7的新特性
// e=new ThirdException();
throw e;
}
}

static class FirstException extends Exception {

public FirstException(String msg) {
super(msg);
}
}

static class SecondException extends Exception {

public SecondException(String msg) {
super(msg);
}
}

static class ThirdException extends Exception {

public ThirdException(String msg) {
super(msg);
}
}

}

如上在 rethrow 方法中,catch 块捕获的异常并没有出现在 throws 从句中。Java7 编译器会分析完整的 try 代码块以检查从 catch 块中什么类型的异常被抛出和重新抛出。

删除元素

Java8 开始,可以使用 Collection#removeIf() 方法删除满足特定条件的元素,如

List<Integer> list = new ArrayList<>();
for (int i = 1; i <= 10; ++i) {
list.add(i);
}

list.removeIf(filter -> filter % 2 == 0); /* 删除list中的所有偶数 */
System.out.println(list); /* [1, 3, 5, 7, 9] */

Reference

参考资料 Java 9 集合工厂方法 参考资料 Java 7 try-with-resources 语句,自动资源释放,提高容错率!